--[[## ft=lua ##################################################################
  This file is licensed under [WTFPL v2](https://www.wtfpl.net/).  You may read
  the full license in the file LICENSE or by following the web link.

  UTILS - a grab bag of homeless features waiting a proper packaging.
--############################################################################]]

local table = require 'table'

local M = {}

--[[#####################################
  A wrapper around `require()` that tries to require a package and, failing
  this, falls back to a provided default.  This is useful for speculatively
  loading wrapper modules and degrading gracefully, e.g.:

        ```
        local table = requirex('tablex', 'table')

        -- ...

        if table.dump then table.dump(foo) end
        ```

--####################################]]
local function requirex(module, default)
  local e, m, f = pcall(require, module)
  if e then return m, f else return require(default) end
end

--[[####################################
  A wrapper around the usual error pattern of returning an error code and a
  value as a pairing.  If the error code is falsey, the value is the error
  message (which is printed) and the third value, if present, is used as the
  OS return code (true if not present).
--####################################]]
local function error_check(err, val, code)
  if not err then
    print(val)
    os.exit(code or false, true)
  end
  return val
end

--[[####################################
  Defers errors without interrupting tasks. Especially useful in test suites.
  Returns a table that contains a list of errors encountered and two functions
  for tracking and displaying both:
    - :add(message) - adds an error message to the list
    - :check() - checks for the existence of any errors and throws the whole
                 list if any have been reported.
  Note that there can be several different error_handlers active in a program.
  Each exists independently and independently tracks its own list of errors.
--####################################]]
local function error_handler()
    local errors = {}
    return {
        add = function(_self, e)
            table.insert(errors, e)
        end,
        check = function(self)
            if #errors > 0 then
                error('Errors recorded:\n\t- ' .. table.concat(errors, '\n\t- '), 0)
            end
        end
    }
end

--[[####################################
  Adjust a decimal number `n` to `d` significant digits.
--####################################]]
local function significant_digits(n, d)
  assert(type(n) == 'number' and type(d) == 'number', 'both arguments must be numbers')
  assert(math.type(d) == 'integer' and d > 0,         'second argument must be positive integer')

  local mul   = 0
  local div   = 0
  local rc    = n
  local shift = math.modf(math.log10(n))
  if shift < 0 then
    shift = shift - d
  else
    shift = shift - d + 1
  end

  rc = rc * 10 ^ -shift
  rc = math.modf(rc + 0.5)
  rc = rc * 10 ^ shift

  if math.type(n) == 'integer' then
    rc = math.modf(rc)
  end

  return rc
end

M.error_check = error_check
M.error_handler = error_handler
M.requirex = requirex
M.significant_digits = significant_digits

return M
